﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NSubstitute;
using Phoenix.ActionResults;
using Phoenix.Framework;
using Xunit;

namespace Phoenix.Tests.Framework
{
    public class ActionResolverTests
    {
        readonly ActionMatcherController testController;
        readonly IControllerFactory controllerFactory;
        readonly IControllerLocator controllerLocator;
        readonly ActionResolver resolver;
        readonly Type controllerType;

        public ActionResolverTests()
        {
            controllerFactory = Substitute.For<IControllerFactory>();
            controllerLocator = Substitute.For<IControllerLocator>();
            testController = Substitute.For<ActionMatcherController>();
            testController.AwesomeActionAsync().Returns(new Task<ActionResult>(() => null));
            controllerType = typeof(ActionMatcherController);
            controllerLocator.Locate(Arg.Is<ControllerAction>(c => c.Controller == "ActionMatcher")).Returns(controllerType);
            resolver = new ActionResolver();
        }

        [Fact]
        public void matches_exact()
        {
            // arrange
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "ExactMatch", null));
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            var resolve = resolver.ResolveFor(controllerActionContext, controllerType);
            resolve.MethodInfo.Invoke(testController, null);

            // assert
            testController.Received().ExactMatch();
        }

        [Fact]
        public void matches_async()
        {
            // arrange
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "AwesomeAction", null));
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            resolver.ResolveFor(controllerActionContext, controllerType)
                .MethodInfo.Invoke(testController, null);

            // assert
            testController.Received().AwesomeActionAsync();
        }

        [Fact]
        public void matches_optional_data_context()
        {
            // arrange
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "ExactMatch", null), 
                parameters: new[] { ControllerActionParameter.FromObject("") });
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            var resolve = resolver.ResolveFor(controllerActionContext, controllerType);
            resolve
                .MethodInfo.Invoke(testController, null);

            // assert
            testController.Received().ExactMatch();
        }

        [Fact]
        public void matches_most_specific_match()
        {
            // arrange
            var controllerActionParameters = new[] { ControllerActionParameter.FromObject("test") };
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "Overload", null), parameters: controllerActionParameters);
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            var resolve = resolver.ResolveFor(controllerActionContext, controllerType);
            resolve.MethodInfo.Invoke(testController, controllerActionParameters.Select(p=>p.Value).ToArray());


            // assert
            testController.Received().Overload("test");
        }

        [Fact]
        public void matches_exact_returning_task()
        {
            // arrange
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "AwesomeActionAsync", null));
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            resolver.ResolveFor(controllerActionContext, controllerType)
                .MethodInfo.Invoke(testController, null);

            // assert
            testController.Received().AwesomeActionAsync();
        }

        [Fact]
        public void matches_optional_data_context_returning_task()
        {
            // arrange
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "AwesomeActionAsync", null),
                parameters: new[] { ControllerActionParameter.FromObject("arg") });
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            resolver.ResolveFor(controllerActionContext, controllerType)
                .MethodInfo.Invoke(testController, null);

            // assert
            testController.Received().AwesomeActionAsync();
        }

        [Fact]
        public void matches_with_query_string_collection()
        {
            // arrange
            var queryStringArguments = new[] {new KeyValuePair<string, string>("arg", "value")};
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "QueryString", null,
                queryStringArguments));
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            resolver.ResolveFor(controllerActionContext, controllerType)
                .MethodInfo.Invoke(testController, new object[]{new QueryStringArgumentsCollection(queryStringArguments)});

            // assert
            testController.Received().QueryString(Arg.Is<QueryStringArgumentsCollection>(a=>a["arg"] == "value"));
        }

        [Fact]
        public void matches_with_query_string_collection_when_not_requested()
        {
            // arrange
            var queryStringArguments = new[] { new KeyValuePair<string, string>("arg", "value") };
            var controllerActionContext = PhoenixObjectMother.CreateContext(new ControllerAction("ActionMatcher", "NoQueryString", null,
                queryStringArguments));
            controllerFactory.Create(typeof(ActionMatcherController), controllerActionContext).Returns(testController);

            // act
            resolver.ResolveFor(controllerActionContext, controllerType)
                .MethodInfo.Invoke(testController, null);

            // assert
            testController.Received().NoQueryString();
        }

        public class ActionMatcherController : Controller
        {
            public virtual ActionResult ExactMatch()
            {
                return null;
            }

            public virtual ActionResult Overload()
            {
                return null;
            }

            public virtual ActionResult Overload(string specific)
            {
                return null;
            }

            public virtual Task<ActionResult> AwesomeActionAsync()
            {
                return null;
            }

            public virtual ActionResult QueryString(QueryStringArgumentsCollection args)
            {
                return null;
            }

            public virtual ActionResult NoQueryString()
            {
                return null;
            }
        }
    }
}